package com.dennytech.tac.utils;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class DefaultLockManager<K> implements LockManager<K> {
	private LinkedList<Ref> reuseList;
	private HashMap<K, Ref> map;

	public DefaultLockManager(int capacity) {
		reuseList = new LinkedList<Ref>();
		map = new HashMap<K, Ref>(capacity);
	}

	public DefaultLockManager() {
		this(16);
	}

	private ReentrantLock retainLock(K key) {
		synchronized (map) {
			Ref ref = map.get(key);
			if (ref == null) {
				ref = reuseList.poll();
				if (ref == null)
					ref = new Ref();
				map.put(key, ref);
			}
			++ref.refCount;
			return ref.lock;
		}
	}

	private ReentrantLock releaseLock(K key) {
		synchronized (map) {
			Ref ref = map.get(key);
			if (ref == null)
				return null;
			if (--ref.refCount == 0)
				map.remove(key);
			return ref.lock;
		}
	}

	@Override
	public void lock(K key) {
		ReentrantLock lock = retainLock(key);
		lock.lock();
	}

	@Override
	public void lockInterruptibly(K key) throws InterruptedException {
		ReentrantLock lock = retainLock(key);
		lock.lockInterruptibly();
	}

	@Override
	public boolean tryLock(K key, long time, TimeUnit unit)
			throws InterruptedException {
		ReentrantLock lock = retainLock(key);
		return lock.tryLock(time, unit);
	}

	@Override
	public boolean tryLock(K key) {
		ReentrantLock lock = retainLock(key);
		return lock.tryLock();
	}

	@Override
	public void unlock(K key) {
		ReentrantLock lock = releaseLock(key);
		if (lock == null)
			throw new IllegalMonitorStateException();
		lock.unlock();
	}

	public int holding() {
		return map.size();
	}

	public int reuses() {
		return reuseList.size();
	}

	@Override
	public String toString() {
		return "DefaultLockManager, holding=" + holding() + ", reuses="
				+ reuses();
	}

	private static class Ref {
		ReentrantLock lock = new ReentrantLock();
		int refCount = 0;
	}
}
